package org.apereo.cas.ticket.code;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.principal.AbstractWebApplicationService;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.configuration.support.Beans;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.support.oauth.OAuth20GrantTypes;
import org.apereo.cas.support.oauth.OAuth20ResponseTypes;
import org.apereo.cas.support.oauth.util.OAuth20Utils;
import org.apereo.cas.ticket.ExpirationPolicy;
import org.apereo.cas.ticket.ExpirationPolicyBuilder;
import org.apereo.cas.ticket.Ticket;
import org.apereo.cas.ticket.TicketGrantingTicket;
import org.apereo.cas.ticket.UniqueTicketIdGenerator;
import org.apereo.cas.util.DefaultUniqueTicketIdGenerator;
import org.apereo.cas.web.y9.util.Y9Context;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;

import lombok.RequiredArgsConstructor;
import lombok.val;

/**
 * Default OAuth code factory.
 *
 * @author Jerome Leleu
 * @since 5.0.0
 */
@RequiredArgsConstructor
public class OAuth20DefaultOAuthCodeFactory implements OAuth20CodeFactory {

    /**
     * Default instance for the ticket id generator.
     */
    protected final UniqueTicketIdGenerator oAuthCodeIdGenerator;

    /**
     * ExpirationPolicy for tokens.
     */
    protected final ExpirationPolicyBuilder<OAuth20Code> expirationPolicy;

    /**
     * Services manager.
     */
    protected final ServicesManager servicesManager;

    private RedisOperations<Object, Object> redisOperations; //y9 add

    public OAuth20DefaultOAuthCodeFactory(final ExpirationPolicyBuilder<OAuth20Code> expirationPolicy, final ServicesManager servicesManager) {
        this(new DefaultUniqueTicketIdGenerator(), expirationPolicy, servicesManager);
    }

    @Override
    public OAuth20Code create(final Service service, final Authentication authentication, final TicketGrantingTicket ticketGrantingTicket, final Collection<String> scopes, final String codeChallenge, final String codeChallengeMethod, final String clientId,
        final Map<String, Map<String, Object>> requestClaims, final OAuth20ResponseTypes responseType, final OAuth20GrantTypes grantType) {

        val expirationPolicyToUse = determineExpirationPolicyForService(clientId);
        val codeId = this.oAuthCodeIdGenerator.getNewTicketId(OAuth20Code.PREFIX);
        val oauthCode = new OAuth20DefaultCode(codeId, service, authentication, expirationPolicyToUse, ticketGrantingTicket, scopes, codeChallenge, codeChallengeMethod, clientId, requestClaims, responseType, grantType);
        if (ticketGrantingTicket != null) {
            ticketGrantingTicket.getDescendantTickets().add(oauthCode.getId());
        }

        // y9 add start
        if (redisOperations == null) {
            RedisIndexedSessionRepository sessionRepository = Y9Context.getBean(RedisIndexedSessionRepository.class);
            redisOperations = sessionRepository.getSessionRedisOperations();
        }
        if (redisOperations != null) {
            AbstractWebApplicationService webApplicationService = (AbstractWebApplicationService)service;
            List<Object> redirectUriList = webApplicationService.getAttributes().get("redirect_uri");
            String redirectUri = (String)redirectUriList.get(0);
            String serviceTicketId = "";

            Map<String, Service> services = ticketGrantingTicket.getServices();
            for (Map.Entry<String, Service> entry : services.entrySet()) {
                AbstractWebApplicationService serviceImpl = (AbstractWebApplicationService)entry.getValue();
                String id = serviceImpl.getId();
                if (id.equals(redirectUri)) {
                    serviceTicketId = entry.getKey();
                    break;
                }
            }

            String key = "y9CodeAndSt:" + oauthCode.getId();
            redisOperations.opsForValue().set(key, serviceTicketId, 12, TimeUnit.HOURS);
        }
        // end

        return oauthCode;
    }

    @Override
    public Class<? extends Ticket> getTicketType() {
        return OAuth20Code.class;
    }

    private ExpirationPolicy determineExpirationPolicyForService(final String clientId) {
        val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(this.servicesManager, clientId);
        if (registeredService != null && registeredService.getCodeExpirationPolicy() != null) {
            val policy = registeredService.getCodeExpirationPolicy();
            val count = policy.getNumberOfUses();
            val ttl = policy.getTimeToLive();
            if (count > 0 && StringUtils.isNotBlank(ttl)) {
                return new OAuth20CodeExpirationPolicy(count, Beans.newDuration(ttl).getSeconds());
            }
        }
        return this.expirationPolicy.buildTicketExpirationPolicy();
    }
}
